
#include <ql/ql.h>

#include <ql/stream.h>

#include "ut.h"

#ifdef QL_GC_DEBUG

#define QL_TREE_NS(name)         gc_##name
#define QL_TREE_KEY_TYPE         struct QlObj *
#define QL_TREE_COMPARE(a, b)    (int)(a - b)
#define QL_TREE_XMALLOC          ql_malloc
#define QL_TREE_XFREE            ql_free
#define QL_TREE_ADD_NODE_REQUIRED
#define QL_TREE_FIND_NODE_REQUIRED
#define QL_TREE_CLEAR_REQUIRED
#define QL_TREE_COUNT_REQUIRED

#include <qltemplates/rb_tree.h>

struct GcCallbackContext
{
    int         objs_disposed;
    gc_Tree     gc_tree;
};

static void gc_dispose_cb(void * ctx, struct QlObj * obj)
{
    struct GcCallbackContext * gcc = (struct GcCallbackContext *)ctx;
    gc_add_node(&gcc->gc_tree, obj);
}


static void * mem_stream_realloc(void * p, size_t s)
{
    return ql_realloc(p, s);
}

static void mem_stream_free(void * p)
{
    ql_free(p);
}

static void gctst1()
{
    struct QlAllocContext * c;
    struct GcCallbackContext gcc = { 0 };
    struct QlId * id1;
    struct QlInt * int1;
    struct QlDouble * dbl1;
    struct QlCdr * cdr1;
    struct QlObj * nil = ql_get_nil();
    struct QlObj * t = ql_get_t();
    char * stream_data = NULL;
    size_t stream_pos = 0;
    struct QlStream * s = ql_create_memory_stream(&stream_data, 0, false, &mem_stream_realloc, &mem_stream_free);

    QL_UT_BEGIN("gctst1");

    gc_init_tree(&gcc.gc_tree);

    c = ql_create_alloc_context();
    QL_UT_VERIFY_CRITICAL((c != NULL) &&
        (nil != NULL) && (ql_type(nil) == QL_OBJ_NIL) &&
        (t != NULL) && (ql_type(t) == QL_OBJ_T));

    ql_gc_register_dispose_callback(c, &gcc, &gc_dispose_cb);

    id1 = ql_alloc_id(c, "a");
    int1 = ql_alloc_integer(c, 45);
    dbl1 = ql_alloc_double(c, 12.54);
    cdr1 = ql_alloc_cdr(c);

    cdr1->car = (struct QlObj *)int1;
    cdr1->cdr = ql_alloc_cdr(c);
    cdr1->cdr->car = t;
    cdr1->cdr->cdr = ql_alloc_cdr(c);
    cdr1->cdr->cdr->car = (struct QlObj *)dbl1;

    ql_write_as_string(cdr1, s);
    ql_stream_putc(s, '\0');
    QL_UT_VERIFY(0 == strcmp(stream_data + stream_pos, "(45 t 12.54)"));
    stream_pos = ql_stream_pos(s);

    {
        struct QlCdr * cdr2 = cdr1->cdr;
        struct QlCdr * cdr3 = cdr2->cdr;

        QL_UT_VERIFY_SILENT(cdr3->cdr == NULL);
        
        gc_tree_clear(&gcc.gc_tree);

        ql_gc_collect(c);

        /* test that all the objects were deallocated correctly */
        QL_UT_VERIFY_SILENT(
            (NULL != gc_find_node(&gcc.gc_tree, (struct QlObj *)cdr1)) &&
            (NULL != gc_find_node(&gcc.gc_tree, (struct QlObj *)int1)) &&
            (NULL != gc_find_node(&gcc.gc_tree, (struct QlObj *)dbl1)) &&
            (NULL == gc_find_node(&gcc.gc_tree, (struct QlObj *)id1)) &&
            (NULL != gc_find_node(&gcc.gc_tree, (struct QlObj *)cdr2)) &&
            (NULL != gc_find_node(&gcc.gc_tree, (struct QlObj *)cdr3))
            );
    }
    
    ql_free_alloc_context(c);
    gc_uninit_tree(&gcc.gc_tree);
    QL_UT_END();
}


#endif

void test_gc()
{
#ifdef QL_GC_DEBUG
    gctst1();
#else
    QL_UT_WARNING("gc left untested");
    fputs("*** WARNING: gc left untested in this configuration ***", stderr);
#endif
}
